<!--固件升级弹框

由于市面上没有好的小程序dfu升级代码
我是看到了一个开源的dfu升级代码但是人家用的是原生小程序写的（这个代码其实也不行很多东西也是不明不白）
我给他变成uniapp版本了 代码量巨多 不建议自己修改
-->
<template>
  <div>
    <fuiBackdrop background="rgba(0,0,0,0.3)" :show="dfuDialogFlag" @click="onTap">
      <div class="DialogContent">
        <div class="titleImg">
          <image class="logo" :src="staticUrl + '/image/img_update.png'" mode="widthFix">
          </image>
        </div>
        <div class="titleName">检测到新固件</div>

        <div class="tips">
          <div>升级说明:</div>
          <div>{{ explain }}</div>
        </div>

        <div class="myBtn">
          <fui-button style="width: 80%;" size="26" background="#0659c7" radius="96rpx" @click="updateBtn">立即升级
          </fui-button>
        </div>

      </div>
    </fuiBackdrop>

    <fui-loading v-if="loadFlag" text="等待升级..." type="col" :isFixed="true" :isMask="true"></fui-loading>
    <fui-loading v-if="updateFlag" :text="update" type="col" :isFixed="true" :isMask="true"></fui-loading>
  </div>
</template>
<script>
import fuiBackdrop from "@/components/firstui/fui-backdrop/fui-backdrop.vue"
import fuiButton from "@/components/firstui/fui-button/fui-button.vue"
import fuiLoading from "@/components/firstui/fui-loading/fui-loading.vue"
import {
  mapState,
  mapMutations
} from 'vuex';
import {
  ab2hex,
  bleWriteTo90001,
  bleWriteTo90003, formater4length,
  parseResponse,
  sendCreate, sendCreatePackage,
  sendEXECUTE,
  sendPRN,
  writeBleBin, writeBleDat
} from "../../../utils/dfu";
import {crc32} from "../../../utils/dfu/crc32";
import bleUtils from "../../../utils/dfu/bleUtils";


const SINGLE_PACK_SIZE = 4096;

const CONTROL_OPCODES = {
  CREATE: 0x01,
  SET_PRN: 0x02,
  CALCULATE_CHECKSUM: 0x03,
  EXECUTE: 0x04,
  SELECT: 0x06,
  RESPONSE_CODE: 0x60,
};

const STEP = {
  // 下载
  DOWNFILE: 1,
  // 解压
  UNZIPFILE: 2,
  // 扫描
  SCANDEVICE: 3,
  RESULT: 4
};

const CONTROL_PARAMETERS = {
  COMMAND_OBJECT: 0x01,
  DATA_OBJECT: 0x02,
  // size: Object size in little endian, set by caller.
  // vale: Number of packets to be sent before receiving a PRN, set by caller. Default == 0.
};

let dat_dataStr = ''


export default {
  name: "UpdateDialog",
  components: {
    fuiBackdrop,
    fuiButton,
    fuiLoading
  },
  props: {
    updateDescription: {
      type: String,
    },
    power: {
      type: Number,
    },
  },
  watch: {
    updateDescription: {
      handler(val) {
        console.log("当前的val", val)
        this.explain = val
      }
    },
    power: {
      handler(val) {
        console.log("当前的powerval", val)
        this.powerNumber = val
      }
    },
    dfuDevices: {
      handler(val) {
        console.log("当前的dfuDevicesNameval", val.name)
        this.dfuDevicesName = val.name
      }
    }
  },
  data() {
    return {
      staticUrl: this.$config.StaticUrl,

      powerNumber: 0,
      dfuDevicesName: "",

      //   升级说明
      explain: "",
      loadFlag: false,
      updateFlag: false,
      update: "",


      dat_data: "",
      bin_data: "",
      imageBuf: "",

      // 升级成功后蓝牙不需要连接
      UpdateState: false,
      // 防止ios 重复连接
      // 连接设备状态
      connectState: false,
      // 连接设备是否在连接
      connectting: false,
      device: "",
      // mac地址
      alreadyConnMAC: "",

      controlPointCharacteristicUUID: "8EC90001-F315-4F60-9FB8-838830DAEA50",
      packetCharacteristicUUID: "8EC90002-F315-4F60-9FB8-838830DAEA50",

      expectedCRC: "",
      bin_offset: ""
    }
  },
  methods: {
    ...mapMutations(['isDFU', 'setTotal', 'setCount', 'setSendType', 'setDfuDialogFlag']),
    // 立即升级按钮
    updateBtn() {
      console.log("点击了")


      if (this.power < 15) {
        this.$toast('头环电量大于15%后重试', {
          duration: 3000
        });

      } else {
        this.isDFU(true)
        bleUtils.closeBluetoothAdapter({}).then(res => {
          console.log("断开", res)
        });

        this.setDfuDialogFlag(false)
        this.loadFlag = true


        let that = this
        const targetPath = wx.env.USER_DATA_PATH
        // 文件下载
        wx.downloadFile({
          url: 'https://xch.jseihc.com/public/platform/headband_dfu_packet/EI-B100_DFU_3.0.6.zip',
          success(res) {
            console.log(res)
            if (res.statusCode === 200) {
              const filemgr = uni.getFileSystemManager()
              filemgr.saveFile({
                tempFilePath: res.tempFilePath,
                filePath: targetPath + '/firmware.zip',
                success(result) {
                  console.log(result)
                  filemgr.unzip({
                    zipFilePath: targetPath + '/firmware.zip',
                    targetPath: targetPath,
                    success: (unzipRes) => {
                      console.log("解压成功:" + JSON.stringify(unzipRes))
                      filemgr.readFile({
                        filePath: targetPath + '/manifest.json',
                        encoding: 'binary',
                        complete: (readFileRes) => {
                          console.log(readFileRes);

                          filemgr.readFile({
                            filePath: targetPath + "/app.dat",
                            complete: (datRes) => {
                              console.log("dat", datRes)
                              var dat_data = ab2hex(datRes.data);
                              let expectedCRC = crc32(datRes.data);
                              this.expectedCRC = expectedCRC
                              console.log(expectedCRC + " 长度1：" + datRes.data.byteLength);
                              dat_dataStr = datRes.data
                              this.dat_data = datRes.data


                            }
                          })

                          filemgr.readFile({
                            filePath: targetPath + "/app.bin",
                            complete: (binRes) => {
                              var bin_data = ab2hex(binRes.data);
                              let expectedCRC = crc32(binRes.data);
                              console.log(expectedCRC + " 长度2：" + binRes.data.byteLength);
                              that.expectedCRC = expectedCRC
                              that.imageBuf = binRes.data;
                              console.log("total", that.total)
                              that.setTotal(parseInt(that.imageBuf.byteLength / SINGLE_PACK_SIZE) + 1);
                              console.log("total2", that.total)
                              that.bin_data = binRes.data

                            }
                          })

                          setTimeout(() => {
                            that.callbackStep(STEP.SCANDEVICE)
                          }, 1000)

                        }
                      })
                    }
                  })
                }
              })
            }
          }
        })
      }
    },

    callbackStep(step) {
      console.log("进入方法了")
      switch (step) {
        case STEP.DOWNFILE: // 去下载固件
          console.log("下载")
          break;
        case STEP.UNZIPFILE:
          console.log("解压")
          // this.unzip()
          break
        case STEP.SCANDEVICE:
          console.log("扫描设备")
          if (!this.UpdateState) {
            this.scanDevice()
          }
          break
        case STEP.RESULT:
          console.log("升级结果")
          this.UpdateState = true
          break
      }
    },

    // 扫描设备
    scanDevice() {
      console.log("扫描55555555")
      console.log(",,,,,,,,,,", dat_dataStr)
      console.log("imagebuf", this.imageBuf)
      bleUtils.openBluetoothAdapter({}).then(function (res) { //初始化蓝牙模块儿
        return bleUtils.getBluetoothAdapterState({}) //获取适配器状态
      }).then(res => {
        if (res.available) { //蓝牙可用
          bleUtils.startBluetoothDevicesDiscovery({
            //services: ["17FF"], //过滤，只搜索微信硬件设备
            allowDuplicatesKey: true,
            interval: 0.1
          }).then((res2) => {
            console.log("res2", res2)
            this.bleCallback()
          })
        }
      })
    },

    // 处理蓝牙状态
    bleCallback() {
      console.log("进入蓝牙状态")
      bleUtils.onBluetoothAdapterStateChange((res) => { //蓝牙状态回调
        console.log(res)
        if (!res.available) {
          console.log("蓝牙没连接")
          return false
        }
      })

      bleUtils.onBLEConnectionStateChange((res) => { //连接状态回调
        if (!res.connected) {
          console.log("断开连接")

          this.connectState = false
          this.connectting = false

          setTimeout(() => {
            if (!this.UpdateState) {
              this.callbackStep(STEP.SCANDEVICE)
            }
          }, 3000)
        }
      })

      // 易爱普通设备
      let EIDevice = []
      // 易爱DFU设备
      let EIDFUDevice = []

      setTimeout(() => {
        console.log("设备列表", EIDevice)
        if (EIDevice.length > 0 && !this.connectState && !this.connectting) {
          console.log("进入普通")
          // EI普通设备
          this.connectDevice(EIDevice[0])
          this.alreadyConnMAC = EIDevice[0].deviceId
        } else if (EIDFUDevice.length > 0 && !this.connectState && !this.connectting) {
          console.log("进入dfu")
          // dfu设备
          this.connectDfuDevice(EIDFUDevice[0])
        }
        // else {
        //   console.log("没有找到设备")
        //   closeBluetoothAdapter({})
        // }

      }, 4000)

      let devicesName = this.dfuDevicesName
      bleUtils.onBluetoothDeviceFound((res) => { //监听寻找到新设备
        let devices = res.devices
        if (devices) {
          for (let i = 0; i < devices.length; i++) {
            if (devices[i].name.indexOf(devicesName) !== -1) {
              EIDevice.push(devices[i])
            } else if (devices[i].name.indexOf('DfuTarg').toString() !== -1) {
              EIDFUDevice.push(devices[i])
            }
          }
        }
      })
    },

    // 连接普通设备
    connectDevice(device) {
      console.log("进入普通方法了")
      console.log("普通方法", device.deviceId)
      this.connectting = true
      bleUtils.createBLEConnection({
        deviceId: device.deviceId,
        timeOut: 5000
      }).then(res => {
        console.log("cccc", res)
        //设备链接成功后记得停止扫描
        bleUtils.stopBluetoothDevicesDiscovery({})
        this.connectState = true;
        this.connectting = false;

        bleUtils.getBLEDeviceServices({
          deviceId: device.deviceId
        }).then(res2 => {
          console.log("获取设备对应的服务:" + JSON.stringify(res2))
          for (let i = 0; i < res2.services.length; i++) {
            // 获取指定服务设备
            if (res2.services[i].uuid.indexOf('FE59') !== -1) {
              device.serverFE59 = res2.services[i]
              break
            }
          }

          bleUtils.getBLEDeviceCharacteristics({ //获取服务fe59对应的特征值90003和90002
            deviceId: device.deviceId,
            serviceId: device.serverFE59["uuid"]
          }).then(res3 => {
            console.log("获取服务对应的特征值:" + JSON.stringify(res3))
            for (let i = 0; i < res3.characteristics.length; i++) {
              console.log("循环", res3.characteristics[i])
              if (res3.characteristics[i].uuid.indexOf('8EC90003-') !== -1) {
                device.characteristic90003 = res3.characteristics[i]
              }
            }


            console.log("dev", device)
            bleUtils.notifyBLECharacteristicValueChange({ //开启90003的notify
              deviceId: device.deviceId,
              serviceId: device.serverFE59["uuid"],
              characteristicId: device.characteristic90003["uuid"],
              state: true
            }).then(res4 => {
              console.log("开启notify", res4)
              let buffer = new ArrayBuffer(1)
              let dataView = new DataView(buffer)
              dataView.setUint8(0, 0x01)

              bleWriteTo90003(device, dataView, buffer).then(res5 => {
                console.log("写入了", res5)
                setTimeout(() => {
                  bleUtils.closeBluetoothAdapter({}) //关闭adapter,否则后面会在部分Android机上搜不到dfu
                  this.connectting = false;

                  this.callbackStep(STEP.SCANDEVICE)
                }, 1000)
              })

            })
          })
        })


      })
    },

    // 连接dfu设备
    connectDfuDevice(device) {
      console.log("dat_dataLength111", dat_dataStr)
      console.log("进入dfu方法了", device.deviceId)
      this.connectting = true;
      bleUtils.createBLEConnection({
        deviceId: device.deviceId,
        timeOut: 5000
      }).then(res => {
        console.log("成功后的device", res)
        this.isDFU(true)
        this.connectState = true;
        this.connectting = false;
        bleUtils.stopBluetoothDevicesDiscovery({})

        bleUtils.getBLEDeviceServices({
          deviceId: device.deviceId
        }).then(res2 => {
          console.log("获取设备对应的服务222:", res2)
          for (let i = 0; i < res2.services.length; i++) {
            if (res2.services[i].uuid.indexOf('FE59') !== -1) {
              device.serverFE59 = res2.services[i]
              break;
            }
          }

          console.log("dev555", device)

          bleUtils.getBLEDeviceCharacteristics({ //获取服务fe59对应的特征值90003和90002
            deviceId: device.deviceId,
            serviceId: device.serverFE59["uuid"]
          }).then(res3 => {
            console.log("获取特征码2", res3)
            for (let i = 0; i < res3.characteristics.length; i++) {
              console.log("循环1", res3.characteristics[i])
              if (res3.characteristics[i].uuid === this.controlPointCharacteristicUUID) {
                console.log("characteristic90001", res3.characteristics[i])
                device.characteristic90001 = res3.characteristics[i]
              }
              if (res3.characteristics[i].uuid === this.packetCharacteristicUUID) {
                console.log("characteristic90002", res3.characteristics[i])
                device.characteristic90002 = res3.characteristics[i]
              }
            }
            console.log("dev", device)
            this.device = device
            setTimeout(() => {


              bleUtils.notifyBLECharacteristicValueChange({ //开启90001的notify
                deviceId: device.deviceId,
                serviceId: device.serverFE59["uuid"],
                characteristicId: device.characteristic90001["uuid"],
                state: true
              }).then(res4 => {
                console.log("开启notify55", res4)
                this.centralizedOnBLECharacteristicValueChange()
                //传输init packet
                let buffer = new ArrayBuffer(2)
                let dataView = new DataView(buffer)
                //写入通道指令
                dataView.setUint8(0, 0x06)
                dataView.setUint8(1, 0x01)

                setTimeout(() => {
                  bleWriteTo90001(device, dataView, buffer, 0)
                }, 2000)
              })
            }, 800)
          })
        })
      })
    },

    // 此处开始统一监听蓝牙传输过来的数据
    centralizedOnBLECharacteristicValueChange() {
      try {
        console.log("进入了123123123123")
        bleUtils.onBLECharacteristicValueChange((res) => {
          console.log("监听到数据", res)
          console.log("send", this.sendType)
          console.log("dat_dataLength", this.dat_data.byteLength)
          switch (this.sendType) {
            case 0:
              console.log('校验数据')
              this.controlPointNotificationHandler(res)
              break
            case 1:
              console.log('case1')
              this.dataEventListener(res)
              break
            case 2:
              console.log('case2')
              this.dataEventListener(res);
              break;
            case 3:
              console.log('case3')
              this.dataEventListener(res);
              break;
            case 4:
              if (ab2hex(res.value)) {
                console.log('case4')
                this.controlPointNotificationHandler(res);
              }
              break;
            case 5:
              console.log('case5')
              this.controlPointNotificationHandler(res);
              break;
            case 6:
              console.log('case6')
              this.dataEventListener(res);

              break;
            case 7:
              this.controlPointNotificationHandler(res);
              console.log('case7')
              break;
            case 8:
              console.log('case8')
              // this.updateBtn()
              this.controlPointNotificationHandler(res);
              break;
            case 9:
              console.log('case9')
              this.dataEventListener(res);
              break;
            default:
              break;
          }
        })

      } catch (error) {
        console.log('统一发送 error', error);
      }


    },


    controlPointNotificationHandler(event) {
      let that = this
      console.log("[[[[[")
      const response = event.value;
      const parsedResponse = parseResponse(response);
      const responseOpCode = parsedResponse.responseOpCode;
      console.log('line 688 >>', parsedResponse);
      console.log("[[[[[dev", this.device)
      console.log("responseOpCode", responseOpCode)

      switch (responseOpCode) {
        case CONTROL_OPCODES.CREATE:
          console.log('CREATE')
          sendPRN(this.device)
          break;
        case CONTROL_OPCODES.SET_PRN:
          console.log('SET_PRN')
          writeBleDat(this.device, dat_dataStr, 0, this.device)
          break
        case CONTROL_OPCODES.CALCULATE_CHECKSUM:
          console.log('CALCULATE_CHECKSUM');
          sendEXECUTE(this.device)
          break;
        case CONTROL_OPCODES.EXECUTE:
          console.log('EXECUTE');
          var buffer = new ArrayBuffer(2)
          var dataView = new DataView(buffer)
          dataView.setUint8(0, CONTROL_OPCODES.SELECT)
          dataView.setUint8(1, CONTROL_PARAMETERS.DATA_OBJECT)
          bleWriteTo90001(this.device, dataView, buffer, 1)
          break;
        case CONTROL_OPCODES.SELECT:
          console.log('SELECT');
          let skipSendingInitPacket = false;
          let resumeSendingInitPacket = false;
          console.log("this.dat_data", dat_dataStr.byteLength)
          let dat_data = dat_dataStr;
          console.log("init包长度：" + dat_dataStr.byteLength + " offset:" + parsedResponse.data.offset);
          if (parsedResponse.data.offset > 0 && parsedResponse.data.offset <= dat_dataStr.byteLength) {
            if (parsedResponse.data.offset == dat_dataStr.byteLength) {
              skipSendingInitPacket = true;
            } else {
              resumeSendingInitPacket = true;
            }
          }
          if (parsedResponse.data.offset == 0 && !skipSendingInitPacket) {
            sendCreate(that.device)
            return
          }
          sendEXECUTE(that.device)
          break;
        default:
          console.log("错误")
          break;
      }
    },

    dataEventListener(event) {
      console.log("进入了dataEvent")
      const response = event.value;
      const parsedResponse = parseResponse(response);
      const responseOpCode = parsedResponse.responseOpCode;

      let device = this.device;
      let binData = this.bin_data;
      console.log("eeeeeee", parsedResponse)
      switch (responseOpCode) {
        case CONTROL_OPCODES.CREATE:
          console.log('CREATE(' + this.count + "/" + this.total + ")");


          this.loadFlag = false
          this.updateFlag = true

          this.update = `正在升级中${this.total}/${this.count}`
          setTimeout(() => {
            writeBleBin(device, this.imageBuf.slice(0, 0x1000), 0)
            this.expectedCRC = crc32(this.imageBuf.slice(0, 0x1000));
            console.log("expectedCRC ->  " + this.expectedCRC + "  length " + this.imageBuf.byteLength);
          }, 500)
          break;
        case CONTROL_OPCODES.SET_PRN:
          console.log('SET_PRN');
          break;
        case CONTROL_OPCODES.CALCULATE_CHECKSUM:

          this.setCount(parsedResponse.data.offset / SINGLE_PACK_SIZE) //计数一下发到第几页
          if (parsedResponse.data.offset == binData.byteLength) {
            this.setCount(this.total);
          }
          this.bin_offset = parsedResponse.data.offset;
          console.log(" 对比crc:" + parsedResponse.data.crc32 + " binCrc:" + crc32(binData.slice(0, SINGLE_PACK_SIZE * this.count)));
          if (parsedResponse.data.crc32 != crc32(binData.slice(0, SINGLE_PACK_SIZE * this.count))) {
            const remainLength = binData.byteLength - parsedResponse.data.offset;
            if (remainLength < 0x1000) {
              sendCreatePackage(this.device, formater4length(remainLength));
            } else {
              sendCreatePackage(this.device, formater4length(0x1000));
            }

            return;
          }

          //校验通过才发送执行命令
          console.log("发送通过")
          this.imageBuf = this.imageBuf.slice(0x1000);
          let buffer = new ArrayBuffer(1)
          let dataView = new DataView(buffer)
          //写入通道指令
          dataView.setUint8(0, CONTROL_OPCODES.EXECUTE)
          bleWriteTo90001(device, dataView, buffer, 2)

          break;

        case CONTROL_OPCODES.EXECUTE:
          console.log('EXECUTE', this.count);
          if (this.count != this.total) { // 如果是校验已经发完了，就不用在创建发送命令

            var remainLength = binData.byteLength - this.bin_offset
            if (remainLength < 0x1000) {
              sendCreatePackage(this.device, formater4length(remainLength));
            } else {
              sendCreatePackage(this.device, formater4length(0x1000));
            }
          } else {
            this.updateFlag = false
            wx.showToast({
              title: '升级成功',
              icon: 'success',
            })
            this.isDFU(false)
            this.callbackStep(STEP.RESULT)
            bleUtils.closeBluetoothAdapter({}).then(res => {
              console.log("断开", res)
            });
          }
          break;

        case CONTROL_OPCODES.SELECT:
          console.log('dataEventListener->SELECT');
          let skipSendingInitPacket = false;
          let resumeSendingInitPacket = false;
          console.log(" offset:" + parsedResponse.data.offset);
          console.log("imagebuf", this.imageBuf)

          if (parsedResponse.data.offset > 0 && parsedResponse.data.offset <= this.imageBuf.byteLength) {
            if (parsedResponse.data.offset == this.imageBuf.byteLength) {
              skipSendingInitPacket = true;
            } else {
              resumeSendingInitPacket = true;
              console.log("已发送crc:" + crc32(this.imageBuf.slice(0, parsedResponse.data.offset)))
              this.imageBuf = this.imageBuf.slice(parsedResponse.data.offset)
              console.log("未发送crc:" + crc32(this.imageBuf))
            }
          }

          // 说明设备上没镜像数据
          if (parsedResponse.data.offset == 0) {
            console.log("没有数据包")
            // TODO: Size should not be hard-coded.
            sendCreatePackage(this.device, formater4length(0x1000)); //4096为一页
            return;
          }

          //写入通道指令
          dataView.setUint8(0, CONTROL_OPCODES.EXECUTE)
          bleWriteTo90003(device, dataView, buffer)
          break
      }
    }

  },

  computed: {
    ...mapState(['dfuDialogFlag', 'total', 'count', 'sendType', 'dfuDevices']),
  },
}
</script>
<style scoped lang="scss">

.DialogContent {
  width: 530rpx;
  background-color: white;
  border-radius: 15px;
  padding-bottom: 40rpx;
  z-index: 999999;


  .titleImg {
    width: 100%;

    .logo {
      width: 100%;
      height: 166rpx;
    }
  }

  .titleName {
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 20rpx;
    font-size: 28rpx;
    margin-bottom: 40rpx;
  }


  .tips {
    font-size: 24rpx;
    width: 80%;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: left;
    margin: 0 auto;
  }

  .myBtn {
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 30rpx;

  }
}
</style>